/**
 *
 */
package br.cnpq.webflowah.controle;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import br.cnpq.webflowah.beans.AbstractQualityParameter;
import br.cnpq.webflowah.beans.Metadata;
import br.cnpq.webflowah.beans.Operacao;
import br.cnpq.webflowah.beans.ParameterBoundary;
import br.cnpq.webflowah.beans.PlanQuality;
import br.ufrn.opencopi.db.QualityParameterDB;

/**
 * This class is responsible for execution order of workflow's execution plans.
 * 
 * @author fred
 */
public class SeletorPlanoExecucao {
	private static final Logger log = Logger
			.getLogger(SeletorPlanoExecucao.class);
	// Pode ser usado na fase de adapatacao
	private List<PlanQuality> planQualityList = new ArrayList<PlanQuality>();
	private Map<String, ParameterBoundary> parametersBoundary = null;
	private Map<URI, Metadata> metadatasLocator = null;

	public List<PlanQuality> getPlanQualityList() {
		return planQualityList;
	}

	public void setPlanQualityList(List<PlanQuality> planQualityList) {
		this.planQualityList = planQualityList;
	}

	/**
	 * parameterWeights stores the weight of each parameter. The key is the name
	 * of parameter and the value is the parameter's weight (between 0..1)
	 */

	public List<List<Operacao>> ordenaPlanoExecucao(
			List<List<Operacao>> planos,
			Hashtable<String, Double> parametersWeights) {

		// consulta qual o menor e maior valor dos parametros
		// deve fazer uma consulta no banco de dados que retorna uma hashtable
		// com o o menor e maior valor de cada parametro
		if (parametersBoundary == null)
			parametersBoundary = QualityParameterDB.getBoudariesOfParameters();

		// para cada plano, calcular a qualidade do servico
		Iterator<List<Operacao>> iter = planos.iterator();

		// Cria uma hash que armazena os valores min e max globais de todos os
		// parametros
		Map<String, ParameterBoundary> globalminAndMaxParams = initializeGlobalBoundaries();
		while (iter.hasNext()) {
			List<Operacao> plan = iter.next();

			PlanQuality planQuality = computeAbsolutyQualityPlan(plan,
					parametersWeights);
			log.info(plan + "\nParametros não normalizados: "
					+ planQuality.getNonNormalizedParameters() + "\n");
			planQualityList.add(planQuality);

			// verifique se para este plano os min e max são os globais
			updateGlobalBoundaries(globalminAndMaxParams, planQuality);

		}
		// Normaliza todos os parametros de cada plano
		normalizeAllParameters(planQualityList, globalminAndMaxParams);

		// faz a soma ponderada
		computeWeigthedAverage(planQualityList, parametersWeights);

		// ordenar planos (PlanQuality objects) por qualidade
		ordenarPlanosPorQualidade();
		List<List<Operacao>> planosOrdenados = new ArrayList<List<Operacao>>();
		// popular a lista de planos ordenados (passando de PlanQuality objects
		// para List<Operacao> objects
		for (int i = 0; i < planQualityList.size(); i++) {
			PlanQuality p = planQualityList.get(i);
			planosOrdenados.add(p.getPlano());
			log.debug(p.getPlano() + "\nParametros não normalizados: "
					+ p.getNonNormalizedParameters()
					+ "\nParametros normalizados: "
					+ p.getNormalizedParameters() + "\nQualidade: "
					+ p.getQuality() + "\n");
		}
		metadatasLocator = null;
		QualityParameterDB.clearAbsQualityParameterLocator();
		QualityParameterDB.clearMetadataLocator();
		return planosOrdenados;

	}

	private void computeWeigthedAverage(List<PlanQuality> planQualityList2,
			Hashtable<String, Double> parametersWeights) {
		Iterator<PlanQuality> i = planQualityList2.iterator();
		// para cada plano
		while (i.hasNext()) {
			PlanQuality plan = i.next();
			Iterator<String> ii = plan.getNormalizedParameters().keySet()
					.iterator();
			double planQuality = 0.0d;
			// para cada parametro
			while (ii.hasNext()) {
				String name = ii.next();
				planQuality += (plan.getNormalizedParameter(name) * parametersWeights
						.get(name));
			}
			plan.setQuality(planQuality);
			log.info(plan.getPlano() + "\nQualidade do plano: " + planQuality);
		}
	}

	private void normalizeAllParameters(List<PlanQuality> planQualityList,
			Map<String, ParameterBoundary> globalMinAndMaxParams) {
		Iterator<PlanQuality> i = planQualityList.iterator();
		double normalized = 0.0d;
		String parameter = null;
		while (i.hasNext()) {
			PlanQuality plan = i.next();
			parameter = Metadata.AVALIABILITY;
			normalized = computeNormalizedParameterQualityValue(
					AbstractQualityParameter.BIGGER_IS_BETTER,
					plan.getNonNormalizedParameter(parameter),
					globalMinAndMaxParams.get(parameter).getMinValue(),
					globalMinAndMaxParams.get(parameter).getMaxValue());
			plan.insertNormalizedParameter(parameter, normalized);

			parameter = Metadata.RESPONSING;
			normalized = computeNormalizedParameterQualityValue(
					AbstractQualityParameter.BIGGER_IS_BETTER,
					plan.getNonNormalizedParameter(parameter),
					globalMinAndMaxParams.get(parameter).getMinValue(),
					globalMinAndMaxParams.get(parameter).getMaxValue());
			plan.insertNormalizedParameter(parameter, normalized);

			parameter = Metadata.PERFORMANCE;
			normalized = computeNormalizedParameterQualityValue(
					AbstractQualityParameter.BIGGER_IS_BETTER,
					plan.getNonNormalizedParameter(parameter),
					globalMinAndMaxParams.get(parameter).getMinValue(),
					globalMinAndMaxParams.get(parameter).getMaxValue());
			plan.insertNormalizedParameter(parameter, normalized);

			parameter = Metadata.FRESHNESS;
			normalized = computeNormalizedParameterQualityValue(
					AbstractQualityParameter.SMALLER_IS_BETTER,
					plan.getNonNormalizedParameter(parameter),
					globalMinAndMaxParams.get(parameter).getMinValue(),
					globalMinAndMaxParams.get(parameter).getMaxValue());
			plan.insertNormalizedParameter(parameter, normalized);

			// parameter = Metadata.RESOLUTION;
			// normalized = computeNormalizedParameterQualityValue(
			// AbstractQualityParameter.SMALLER_IS_BETTER,
			// plan.getNonNormalizedParameter(parameter),
			// globalminAndMaxParams.get(parameter).getMinValue(),
			// globalminAndMaxParams.get(parameter).getMaxValue());
			// plan.insertNormalizedParameter(parameter, normalized);

			parameter = Metadata.CORRECTNESS;
			normalized = computeNormalizedParameterQualityValue(
					AbstractQualityParameter.BIGGER_IS_BETTER,
					plan.getNonNormalizedParameter(parameter),
					globalMinAndMaxParams.get(parameter).getMinValue(),
					globalMinAndMaxParams.get(parameter).getMaxValue());
			plan.insertNormalizedParameter(parameter, normalized);

			// parameter = Metadata.PRECISION;
			// normalized = computeNormalizedParameterQualityValue(
			// AbstractQualityParameter.BIGGER_IS_BETTER,
			// plan.getNonNormalizedParameter(parameter),
			// globalminAndMaxParams.get(parameter).getMinValue(),
			// globalminAndMaxParams.get(parameter).getMaxValue());
			// plan.insertNormalizedParameter(parameter, normalized);
			log.debug(plan.getPlano() + "\nParametros normalizados: "
					+ plan.getNormalizedParameters() + "\n");
		}
	}

	private Map<String, ParameterBoundary> initializeGlobalBoundaries() {
		Map<String, ParameterBoundary> boundaries = new HashMap<String, ParameterBoundary>();
		boundaries.put(Metadata.AVALIABILITY, new ParameterBoundary(
				Metadata.AVALIABILITY, Double.MAX_VALUE, Double.MIN_VALUE));
		boundaries.put(Metadata.PERFORMANCE, new ParameterBoundary(
				Metadata.PERFORMANCE, Double.MAX_VALUE, Double.MIN_VALUE));
		boundaries.put(Metadata.RESPONSING, new ParameterBoundary(
				Metadata.RESPONSING, Double.MAX_VALUE, Double.MIN_VALUE));
		boundaries.put(Metadata.FRESHNESS, new ParameterBoundary(
				Metadata.FRESHNESS, Double.MAX_VALUE, Double.MIN_VALUE));
		boundaries.put(Metadata.CORRECTNESS, new ParameterBoundary(
				Metadata.CORRECTNESS, Double.MAX_VALUE, Double.MIN_VALUE));
		return boundaries;
	}

	private void updateGlobalBoundaries(
			Map<String, ParameterBoundary> minAndMaxParams, PlanQuality plan) {
		Iterator<String> i = minAndMaxParams.keySet().iterator();
		// percorre todos os parametros
		while (i.hasNext()) {
			String name = i.next();
			ParameterBoundary globalBoundary = minAndMaxParams.get(name);
			ParameterBoundary planBoundary = getPlanBoundary(name, plan);
			if (globalBoundary.getMinValue() > planBoundary.getMinValue())
				globalBoundary.setMinValue(planBoundary.getMinValue());
			if (globalBoundary.getMaxValue() < planBoundary.getMaxValue())
				globalBoundary.setMaxValue(planBoundary.getMaxValue());
			log.debug("Atual global boundary: " + globalBoundary);
		}

	}

	private ParameterBoundary getPlanBoundary(String parameter, PlanQuality plan) {
		ParameterBoundary boundary = new ParameterBoundary(parameter,
				Double.MAX_VALUE, Double.MIN_VALUE);
		Double value = plan.getNonNormalizedParameter(parameter);
		if (value < boundary.getMinValue())
			boundary.setMinValue(value);
		if (value > boundary.getMaxValue())
			boundary.setMaxValue(value);

		return boundary;
	}

	/**
	 * Ordena a lista de planos de acordo com a prioridade de execucao (mesmo
	 * impacto), de maior para menor prioridade
	 * 
	 * @param planImpactList
	 * @return
	 */
	private void ordenarPlanosPorQualidade() {
		Collections.sort(planQualityList, new Comparator<PlanQuality>() {
			public int compare(PlanQuality o1, PlanQuality o2) {
				PlanQuality p1 = (PlanQuality) o1;
				PlanQuality p2 = (PlanQuality) o2;
				return p1.getQuality() < p2.getQuality() ? +1 : (p1
						.getQuality() > p2.getQuality() ? -1 : 0);
			}
		});
	}

	private PlanQuality computeAbsolutyQualityPlan(List<Operacao> plan,
			Hashtable<String, Double> parametersWeights) {
		PlanQuality planQuality = new PlanQuality();
		planQuality.setPlano(plan);
		Iterator<Operacao> operations = plan.iterator();
		// ParameterBoundary paramBoundary = null;
		Metadata metadata = null;

		// QoS parameters
		double responsing = 0.0d, avaliability = 1.0d, performance = Double.MAX_VALUE;

		// FIXME: Implementar os parametros resolution e precision
		// QoC parameters
		double correctness = 1.0d, freshness = 0.0d;// precision = 1.0d,
													// resolution = 1.0d;

		while (operations.hasNext()) {
			Operacao op = operations.next();
			AbstractQualityParameter absQParameter = null;
			if (metadatasLocator != null
					&& metadatasLocator.containsKey(op.getURI())) {
				metadata = metadatasLocator.get(op.getURI());
			} else {
				metadata = QualityParameterDB.listParametersByService(op
						.getURI().toString());
			}

			responsing += Double.parseDouble(metadata.getQualityParameter(
					Metadata.RESPONSING).getValue());
			avaliability *= Double.parseDouble(metadata.getQualityParameter(
					Metadata.AVALIABILITY).getValue());

			absQParameter = metadata.getQualityParameter(Metadata.PERFORMANCE);
			if (absQParameter != null
					&& performance > Double.parseDouble(absQParameter
							.getValue()))
				performance = Double.parseDouble(absQParameter.getValue());

			absQParameter = metadata.getQualityParameter(Metadata.FRESHNESS);
			if (absQParameter != null && !absQParameter.getValue().isEmpty())
				freshness += Double.parseDouble(absQParameter.getValue());

			absQParameter = metadata.getQualityParameter(Metadata.CORRECTNESS);
			if (absQParameter != null && !absQParameter.getValue().isEmpty())
				correctness *= Double.parseDouble(absQParameter.getValue());
		}
		freshness = freshness / plan.size();

		planQuality.insertNonNormalizedParameter(Metadata.AVALIABILITY,
				avaliability);
		planQuality.insertNonNormalizedParameter(Metadata.RESPONSING,
				responsing);
		planQuality.insertNonNormalizedParameter(Metadata.PERFORMANCE,
				performance);
		planQuality.insertNonNormalizedParameter(Metadata.CORRECTNESS,
				correctness);
		planQuality.insertNonNormalizedParameter(Metadata.FRESHNESS, freshness);

		// double planQualityValue = (responsing * parametersWeights
		// .get(Metadata.RESPONSING))
		// + (avaliability * parametersWeights.get(Metadata.AVALIABILITY))
		// + (performance * parametersWeights.get(Metadata.PERFORMANCE))
		// + (correctness * parametersWeights.get(Metadata.CORRECTNESS))
		// + (freshness * parametersWeights.get(Metadata.FRESHNESS));
		// // + (precision * parametersWeights.get(Metadata.PRECISION))
		// + (resolution * parametersWeights.get(Metadata.RESOLUTION));
		// planQualityValue = planQualityValue / plan.size();
		// planQuality.setQuality(planQualityValue);
		return planQuality;
	}

	/**
	 * Compute the normalized parameter quality
	 * 
	 * @param priority
	 *            - SMALLER_IS_BETTER or BIGGER_IS_BETTER
	 * @param qualityParameterValue
	 *            - value not normalized
	 * @param min
	 *            - minimum value of a specific parameter
	 * @param max
	 *            - maximun value of a specific parameter
	 * @return
	 */
	private double computeNormalizedParameterQualityValue(String priority,
			double qualityParameterValue, double min, double max) {
		double normalizedValue = 0.0;
		if (priority.equals(AbstractQualityParameter.SMALLER_IS_BETTER)) {
			if (min == max) {
				normalizedValue = 1.0;
			} else {
				normalizedValue = (max - qualityParameterValue) / (max - min);
			}
		} else { // if not, BIGGER_IS_BETTER
			if (min == max) {
				normalizedValue = 1.0;
			} else {
				normalizedValue = (qualityParameterValue - min) / (max - min);
			}
		}
		log.debug("Priority: " + priority + "\tMin: " + min + "\tMax: " + max
				+ "\tValue: " + qualityParameterValue + "\tNormalized Value: "
				+ normalizedValue);
		return normalizedValue;
	}

	/**
	 * This method recover all entries in database. This is necessary to
	 * optimize the plan's selection process.
	 * 
	 * @author thiago
	 */
	public void startUp(List<List<Operacao>> operations) {
		parametersBoundary = QualityParameterDB.getBoudariesOfParameters();
		if (metadatasLocator == null)
			metadatasLocator = new HashMap<URI, Metadata>();
		Iterator<List<Operacao>> iterator = operations.iterator();
		while (iterator.hasNext()) {
			Iterator<Operacao> i = iterator.next().iterator();
			while (i.hasNext()) {
				Operacao op = i.next();
				Metadata metadata = QualityParameterDB
						.listParametersByService(op.getURI().toString());
				metadatasLocator.put(op.getURI(), metadata);
			}
		}
	}
}
